#include "ip.h"

#include "arp.h"
#include "ethernet.h"
#include "icmp.h"
#include "net.h"

/**
 * @brief 处理一个收到的数据包
 *
 * @param buf 要处理的数据包
 * @param src_mac 源mac地址
 */
void ip_in(buf_t *buf, uint8_t *src_mac) {
    // TO-DO
    // Step1 检查数据包长度：
    if (buf->len < sizeof(ip_hdr_t))
        return;

    // Step2 对 IP 报头进行详细检查
    ip_hdr_t *header = (ip_hdr_t *)buf->data;

    uint16_t temp = swap16(header->total_len16);
    if (header->version != IP_VERSION_4 || swap16(header->total_len16) > buf->len) {
        return;
    }

    // Step3 校验头部校验和：
    // 先保存头部校验和，再将该头部校验和字段置为 0。
    // 随后调用 checksum16 函数来计算头部校验和，将计算结果与原本的首部校验和字段进行对比。
    uint16_t save = header->hdr_checksum16;
    header->hdr_checksum16 = 0;
    uint16_t checksum = checksum16((uint16_t *)header, header->hdr_len * 4);
    if (checksum == save) {
        header->hdr_checksum16 = save;
    } else {
        return;
    }

    // Step4 对比目的 IP 地址是否为本机的 IP 地址。
    if (memcmp(header->dst_ip, net_if_ip, NET_IP_LEN) != 0) {
        return;
    }

    // Step5 去除填充字段：
    // 如果接收到的数据包的长度大于 IP 头部的总长度字段，说明存在填充字段。
    if (buf->len > swap16(header->total_len16)) {
        buf_remove_padding(buf, buf->len - swap16(header->total_len16));
    }

    // Step6 去掉 IP 报头
    buf_t temp_buf;
    buf_init(&temp_buf, buf->len);
    memcpy(temp_buf.data, buf->data, buf->len);

    buf_remove_header(buf, header->hdr_len * 4);

    // Step7 向上层协议传递数据包：
    // 调用 net_in() 函数向上层传递数据包。
    // 若遇到不能识别的协议类型，调用 icmp_unreachable() 函数返回 ICMP 协议不可达信息。
    // 需要特别注意的是，在 Step 6 中已经去掉了 IP 报头，因此在发出 ICMP 协议报文前，需重新加入 IP 报头，以确保报文格式的完整性。
    if (net_in(buf, header->protocol, header->src_ip) == -1) {
        icmp_unreachable(&temp_buf, header->src_ip, ICMP_CODE_PROTOCOL_UNREACH);
    }
}
/**
 * @brief 处理一个要发送的ip分片
 *
 * @param buf 要发送的分片
 * @param ip 目标ip地址
 * @param protocol 上层协议
 * @param id 数据包id
 * @param offset 分片offset，必须被8整除
 * @param mf 分片mf标志，是否有下一个分片
 */
void ip_fragment_out(buf_t *buf, uint8_t *ip, net_protocol_t protocol, int id, uint16_t offset, int mf) {
    // TO-DO
    // Step1 增加 IP 数据报头部缓存空间
    buf_add_header(buf, sizeof(ip_hdr_t));

    // Step2 填写头部字段：
    // 按照 IP 协议的规范，填写 IP 数据报头部的各个字段，如版本号、首部长度、区分服务、总长度等。
    ip_hdr_t *header = (ip_hdr_t *)buf->data;

    header->version = IP_VERSION_4;
    header->hdr_len = 5;
    header->tos = 0;
    header->total_len16 = swap16(buf->len);

    header->id16 = swap16(id);                                      // 同一个数据报的所有分片都具有相同的标识值。在本实验中，标识字段可从 0 开始计数，然后逐步自增。
    header->flags_fragment16 = swap16((mf << 13) | (offset >> 3));  // MF标志+分片偏移

    header->ttl = 64;
    header->protocol = protocol;

    memcpy(&header->dst_ip, ip, 4);
    memcpy(&header->src_ip, net_if_ip, 4);

    header->hdr_checksum16 = 0;
    header->hdr_checksum16 = checksum16((uint16_t *)header, sizeof(ip_hdr_t));

    // Step3 发送数据：
    // 调用 arp_out() 函数将封装后的 IP 头部和数据发送出去。
    arp_out(buf, ip);
}

/**
 * @brief 处理一个要发送的ip数据包
 *
 * @param buf 要处理的包
 * @param ip 目标ip地址
 * @param protocol 上层协议
 */
void ip_out(buf_t *buf, uint8_t *ip, net_protocol_t protocol) {
    // TO-DO
    // Step1 检查数据报包长：
    // 从上层传递下来的数据报包长是否大于 IP 协议最大负载包长（1500 字节（MTU）减去 IP 首部长度）
    int max_ip_payload_len = 1500 - sizeof(ip_hdr_t);
    if (buf->len > max_ip_payload_len) {
        // Step2 数据报包长超过 IP 协议最大负载包长，进行分片发送：

        // 将数据报包长截断，调用 ip_fragment_out()发送。
        // 最后的一个分片小于或等于 IP 协议最大负载包长，初始化一个 ip_buf，大小等于该分片大小。
        // 注意，最后一个分片的 MF（更多分片标志）应置为 0。

        static uint16_t packet_id_counter = 0;

        // 数据包的标识符
        uint16_t packet_id = packet_id_counter++;

        int offset = 0;
        while (offset < buf->len) {
            int frag_len = (buf->len - offset > max_ip_payload_len) ? max_ip_payload_len : buf->len - offset;

            // 初始化一个 ip_buf
            buf_t ip_buf;
            if (buf_init(&ip_buf, frag_len) == -1)
                return;
            
            // 发送一个分片 ip_buf
            memcpy(ip_buf.data, buf->data + offset, frag_len);
            int mf = (offset + frag_len < buf->len) ? 1 : 0;
            ip_fragment_out(&ip_buf, ip, protocol, packet_id, offset, mf);

            offset += frag_len;
        }

    } else {
        // Step3 数据报包长没有超过 IP 协议最大负载包长，直接发送
        ip_fragment_out(buf, ip, protocol, 0, 0, 0);
    }
}

/**
 * @brief 初始化ip协议
 *
 */
void ip_init() {
    net_add_protocol(NET_PROTOCOL_IP, ip_in);
}